package CoordTransformUtils;

import org.geotools.data.FeatureWriter;
import org.geotools.data.Transaction;
import org.geotools.data.shapefile.ShapefileDataStore;
import org.geotools.data.shapefile.ShapefileDataStoreFactory;
import org.geotools.data.simple.SimpleFeatureIterator;
import org.geotools.data.simple.SimpleFeatureSource;
import org.geotools.feature.simple.SimpleFeatureTypeBuilder;
import org.geotools.geometry.jts.JTS;
import org.geotools.geometry.jts.JTSFactoryFinder;
import org.geotools.referencing.CRS;
import org.geotools.referencing.crs.DefaultGeographicCRS;
import org.locationtech.jts.geom.Coordinate;
import org.locationtech.jts.geom.Geometry;
import org.locationtech.jts.geom.GeometryFactory;
import org.locationtech.jts.io.WKTReader;
import org.locationtech.jts.io.WKTWriter;
import org.opengis.feature.simple.SimpleFeature;
import org.opengis.feature.simple.SimpleFeatureType;
import org.opengis.referencing.crs.CoordinateReferenceSystem;
import org.opengis.referencing.operation.MathTransform;

import java.io.File;
import java.io.Serializable;
import java.util.HashMap;
import java.util.Map;

/**
 * 坐标转换类
 */
public class CoordTransform {

    //设置全局方式（强制经度在前，纬度在后）
    static {
        System.out.println("执行了全局设置语句");
        System.setProperty("org.geotools.referencing.forceXY","true");
//        Hints.putSystemDefault(Hints.FORCE_LONGITUDE_FIRST_AXIS_ORDER,Boolean.TRUE);
    }
    /**
     * 单个Geometry转换时需要使用这几个兑现
    private static GeometryFactory geoFactory = JTSFactoryFinder.getGeometryFactory();
    private static WKTReader wktReader = new WKTReader(geoFactory);
    private static WKTWriter wktWriter = new WKTWriter();
     */
    //这个字符串必须手动写么，有没有其他的更准确的方式呢？
    final String wktMectorStr = "PROJCS[\"World_Mercator\","
            + "GEOGCS[\"GCS_WGS_1984\","
            + "DATUM[\"WGS_1984\","
            + "SPHEROID[\"WGS_1984\",6378137,298.257223563]],"
            + "PRIMEM[\"Greewich\",0],"
            + "UNIT[\"Degree\",0.017453292519943295]],"
            + "PROJECTION[\"Mercator_1SP\"],"
            + "PARAMETER[\"False_Easting\",0],"
            + "PARAMETER[\"False_Northing\",0],"
            + "PARAMETER[\"Central_Meridian\",0],"
            + "PARAMETER[\"latitude_of_origin\",0],"
            + "UNIT[\"Meter\",1]]";

    /**
     * 将字符串表示的wkt几何对象转换为web墨卡托投影表示的几何
     * @param wktStr 字符串表示的wkt几何对象
     * @return 返回转换后的字符串表示的几何
     * @throws Exception
     */
    public String projectGeometry(String wktStr) throws Exception {
        GeometryFactory geoFactory = JTSFactoryFinder.getGeometryFactory();
        WKTReader wktReader = new WKTReader(geoFactory);
        WKTWriter wktWriter = new WKTWriter();
        Geometry geoWKT = wktReader.read(wktStr);
        return wktWriter.write(lonlat2WebMactor(geoWKT));
    }

    /**
     * 经纬度转换为Web墨卡托投影
     * @param geoLonLat 经纬度表示的Geometry
     * @return
     */
    private Geometry lonlat2WebMactor(Geometry geoLonLat) throws Exception {
        try {
            //这里是以OGC wkt的形式定义World Mercator投影，网页地图一般采用这个投影
            //设置经度在前，纬度在后
            CoordinateReferenceSystem crsTarget = CRS.decode("EPSG:3857");
            //投影转换(设置‘宽大处理’？)
            MathTransform transform = CRS.findMathTransform(DefaultGeographicCRS.WGS84, crsTarget);
            return JTS.transform(geoLonLat, transform);
        } catch (Exception error) {
            System.out.println("错误要素的经纬度值：");
            Coordinate[] coords = geoLonLat.getCoordinates();
            System.out.println("顶点数目：" + coords.length + "个。");
            for (Coordinate perCoord : coords) {
                System.out.println(perCoord);
            }
            throw new Exception("Geometry转换失败：" + error.getMessage());
        }
    }

    /**
     * 将经纬度表示的shp文件转换为Web墨卡托表示的shp文件
     * @param shpInput 原始shp文件（4326经纬度表示）
     * @param shpOutput 转换后的结果shp文件（3857Web墨卡托投影）
     */
    public void  projectShp(String shpInput,String shpOutput) {
        try {
            //读取源shp文件
            File shpFileInput = new File(shpInput);
            ShapefileDataStoreFactory shpDSFactory = new ShapefileDataStoreFactory();
            ShapefileDataStore shpDSInput = (ShapefileDataStore) shpDSFactory.createDataStore(shpFileInput.toURI().toURL());
            //创建目标shp文件
            Map<String, Serializable> parms = new HashMap<>();
            File shpFileOutput = new File(shpOutput);
            parms.put(ShapefileDataStoreFactory.URLP.key, shpFileOutput.toURI().toURL());
            ShapefileDataStore shpDSOutput = (ShapefileDataStore) shpDSFactory.createNewDataStore(parms);
            //设置坐标参考
            SimpleFeatureSource fsInput = shpDSInput.getFeatureSource(shpDSInput.getTypeNames()[0]);
//            CoordinateReferenceSystem crs = CRS.parseWKT(wktMectorStr);
            CoordinateReferenceSystem crdWeb=CRS.decode("EPSG:3857");
            shpDSOutput.createSchema(SimpleFeatureTypeBuilder.retype(fsInput.getSchema(), crdWeb));
            //获取write
            FeatureWriter<SimpleFeatureType, SimpleFeature> writeTarget =
                    shpDSOutput.getFeatureWriter(shpDSOutput.getTypeNames()[0], Transaction.AUTO_COMMIT);
            SimpleFeatureIterator feaIteratorInput = fsInput.getFeatures().features();
            int countTransed=0;
            //坐标几何转换并存入新的shp文件中
            try {
                while (feaIteratorInput.hasNext()) {
                    countTransed++;
                    SimpleFeature feaSource = feaIteratorInput.next();
                    //跳过第8个错误的要素(执行成功了)
                    if (countTransed==8) {
                        continue;
                    }
                    SimpleFeature feaTarget = writeTarget.next();
                    feaTarget.setAttributes(feaSource.getAttributes());
                    Geometry geoSource = (Geometry) feaSource.getAttribute("the_geom");
                    feaTarget.setAttribute("the_geom", lonlat2WebMactor(geoSource));
                }
            } catch (Exception errorTrans) {
                System.out.println("第"+countTransed+"个要素转换失败：");
                errorTrans.printStackTrace();
            } finally {
                feaIteratorInput.close();
            }
            writeTarget.write();
            writeTarget.close();
            shpDSOutput.dispose();
            shpDSInput.dispose();
        } catch (Exception error) {
            error.printStackTrace();
        }
    }
}
